package com.ebarlas.fractal.gui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;

import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;

import com.ebarlas.fractal.CompilerFractalService;
import com.ebarlas.fractal.Fractal;
import com.ebarlas.fractal.FractalService;

public class ZoomPanel extends JPanel implements MouseListener {
    private static final long serialVersionUID = 1L;
    
    private int imgWidth;
    private int imgHeight;
    
    private JPanel statusPanel;
    private JLabel imageLabel;
    private JLabel statusLabel;
    private JProgressBar progressBar; 
    
    private FractalService fractalService;
    
    private final Object lock;
    private Fractal fractal;
    private boolean update;
    private boolean zoom;
    private int zoomX;
    private int zoomY;
    
    public ZoomPanel(int imgWidth, int imgHeight) {
        this.imgWidth = imgWidth;
        this.imgHeight = imgHeight;
        
        lock = new Object();
        fractal = null;
        update = false;
        zoom = false;
        zoomX = 0;
        zoomY = 0;

        fractalService = new CompilerFractalService();
        
        initComponents();
        
        new Thread(new Runnable() {
            public void run() {
                executionLoop();
            }
        }).start(); 
    }
    
    public void setFractal(Fractal fractal) {
        // update fractal reference and flag for update execution
        synchronized (lock) {
            this.fractal = fractal;
            update = true;
            lock.notify();
        }        
    }
    
    private void initComponents() {
        setLayout(new BorderLayout());
        
        BufferedImage image = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_ARGB);
        Graphics g = image.getGraphics();
        g.setColor(Color.white);
        g.fillRect(0, 0, imgWidth, imgHeight);
        
        imageLabel = new JLabel();
        imageLabel.setHorizontalAlignment(SwingConstants.CENTER);
        imageLabel.setIcon(new ImageIcon(image));
        add(imageLabel, BorderLayout.CENTER);
        
        statusPanel = new JPanel();
        statusPanel.setLayout(new BorderLayout());
        add(statusPanel, BorderLayout.SOUTH);
        
        progressBar = new JProgressBar();
        statusPanel.add(progressBar, BorderLayout.WEST);
        
        statusLabel = new JLabel();
        statusLabel.setHorizontalAlignment(SwingConstants.RIGHT);
        statusPanel.add(statusLabel, BorderLayout.CENTER);
        
        addMouseListener(this);
    }
    
    private static void zoom(Fractal fractal, int imgWidth, int imgHeight, int x, int y, double scale) {
        double pixelWidth = fractal.getRegionWidth() / imgWidth;
        double pixelHeight = fractal.getRegionHeight() / imgHeight;
        double real = fractal.getRegionX() + pixelWidth * x;
        double imaginary = fractal.getRegionY() + pixelHeight * y;
        
        fractal.setRegionWidth(fractal.getRegionWidth() / scale);
        fractal.setRegionHeight(fractal.getRegionHeight() / scale);
        fractal.setRegionX(real - fractal.getRegionWidth() / scale);
        fractal.setRegionY(imaginary - fractal.getRegionHeight() / scale); 
    }    
    
    private void executionLoop() {
        while(true) {
            try {
                // local copies of shared variables
                Fractal f;
                boolean doZoom;
                int x;
                int y;
                
                // wait until a new execution is desired
                synchronized(lock) {
                    while(!update && !zoom) {
                        lock.wait();
                    }
                    
                    // prefer update over zoom and clear update flag
                    if(update) {
                        update = false;
                        zoom = false;
                    }
                    
                    // access shared variables in synchronized block
                    doZoom = zoom;
                    x = zoomX;
                    y = zoomY;
                    f = fractal;
                }
                
                // update fractal region
                if(doZoom) {
                    zoom(f, imgWidth, imgHeight, x, y, 2.0);
                }
                
                // do pre-exec gui updates in swing thread
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        progressBar.setIndeterminate(true);
                        statusLabel.setText("Computing...");  
                    } 
                });

                // create fractal image
                long start = System.currentTimeMillis();
                final BufferedImage image = fractalService.createFractalImage(f, imgWidth, imgHeight);
                final double duration = (System.currentTimeMillis() - start) / 1000.0;
                
                // do post-exec gui updates in swing thread
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        imageLabel.setIcon(new ImageIcon(image));
                        progressBar.setIndeterminate(false);
                        statusLabel.setText("Time Elapsed: " + duration + " sec");
                    } 
                });
                
                // clear zoom flag and update shared fractal reference
                synchronized (lock) {
                    fractal = f;
                    zoom = false;
                }
            } catch(Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    @Override
    public void mouseClicked(MouseEvent e) { }

    @Override
    public void mouseEntered(MouseEvent e) {}

    @Override
    public void mouseExited(MouseEvent e) {}

    @Override
    public void mousePressed(MouseEvent e) {}

    @Override
    public void mouseReleased(MouseEvent e) { 
        // set zoom flag, zoom coords, and notify
        synchronized(lock) {
            zoomX = e.getX();
            zoomY = e.getY();
            zoom = true;
            lock.notify();
        }
    }
    
}
